package model;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Scanner;

import selection.RankSelection;
import selection.Roulette;
import selection.Selection;
import selection.SigamaScaling;
import selection.Tournament;
import crossover.Crossover;
import crossover.OX1;
import crossover.PMX;
import crossover.SinglePoint;

public class GA {
	public enum SelectionEnum {
	    RANK,ROULETTE,SIGMA,TOURNAMENT
	}
	public enum CrossoverEnum {
	    SINGLE,OX1,PMX
	}
	public enum MutationEnum {
	    SWAP,RANDOM,RSM
	}
	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		
		Country country=Country.getIns();
		country.resetCities();
		System.out.println(country);
		
		ArrayList<Integer> bestArr=new ArrayList<Integer>();
		Scanner scan= new Scanner(new File("tsp-best.txt"));
		while(scan.hasNext())
			bestArr.add(scan.nextInt());
		
		
		int[] bestPathExample=new int[bestArr.size()];
		for(int i=0;i<bestArr.size();i++)
			bestPathExample[i]=bestArr.get(i);
//		int[] bestPathExample={1,8,38,31,44,18,7,28,6,37,19,27,17,43,30,36,46,33,20,47,21,32,39,48,5,42,24,10,45,35,4,26,2,29,34,41,16,22,3,23,14,25,13,11,12,15,40,9};
		double best=calcBestPathExample(bestPathExample);
		
		
		
		System.out.println("best:"+best);
		
		int populationSize = 500;
		int generations=1500;
		double crossoverRate=5;
		double mutationRate=0.8;
		int elitismNumber=4;
		MutationEnum mutationType=MutationEnum.RSM;
		int numOfAlgorythms=1;
		CrossoverEnum cross=CrossoverEnum.SINGLE;
		SelectionEnum select=SelectionEnum.RANK;
		boolean printChromosome=false;
		
		int counter=0;
		while(counter!=args.length){
			if(args[counter].compareTo("-s")==0){
				if(args[counter+1].compareTo("rank")==0)
					select=SelectionEnum.RANK;
				else if(args[counter+1].compareTo("sigma")==0)
					select=SelectionEnum.SIGMA;
				else if(args[counter+1].compareTo("roulette")==0)
					select=SelectionEnum.ROULETTE;
				else if(args[counter+1].compareTo("tournament")==0)
					select=SelectionEnum.TOURNAMENT;
				else 
					throw new Exception("invalid selection arg");
			}
			else if(args[counter].compareTo("-c")==0){
				if(args[counter+1].compareTo("single")==0)
					cross=CrossoverEnum.SINGLE;
				else if(args[counter+1].compareTo("ox1")==0)
					cross=CrossoverEnum.OX1;
				else if(args[counter+1].compareTo("pmx")==0)
					cross=CrossoverEnum.PMX;
				else 
					throw new Exception("invalid crossover arg");
				
			}
			else if(args[counter].compareTo("-m")==0){
				if(args[counter+1].compareTo("swap")==0)
					mutationType=MutationEnum.SWAP;
				else if(args[counter+1].compareTo("random")==0)
					mutationType=MutationEnum.RANDOM;
				else if(args[counter+1].compareTo("rsm")==0)
					mutationType=MutationEnum.RSM;
				else 
					throw new Exception("invalid mutaiton arg");
			}
			else if(args[counter].compareTo("-cr")==0){
				crossoverRate=Integer.parseInt(args[counter+1]);
				if(crossoverRate<0 || crossoverRate>100)
					throw new Exception("invalid crossover rate, please get number between 0-100");
				
			}
			else if(args[counter].compareTo("-e")==0){
				elitismNumber=Integer.parseInt(args[counter+1]);
				if(elitismNumber<0)
					throw new Exception("invalid elite number");
			}
			else if(args[counter].compareTo("-mr")==0){
				mutationRate=Double.parseDouble(args[counter+1]);
				if(mutationRate<0 || mutationRate>1)
					throw new Exception("invalid mutatioonrate please enter float number");
			}
			else if(args[counter].compareTo("-p")==0){
				populationSize=Integer.parseInt(args[counter+1]);
				if(populationSize<1)
					throw new Exception("invalid populationSize number");
			}
			else if(args[counter].compareTo("-g")==0){
				generations=Integer.parseInt(args[counter+1]);
				if(generations<1)
					throw new Exception("invalid generations number");
			}
			else if(args[counter].compareTo("-t")==0){
				numOfAlgorythms=Integer.parseInt(args[counter+1]);
				if(numOfAlgorythms<0)
					throw new Exception("invalid Times number");
			}
			else if(args[counter].compareTo("-ch")==0){
				printChromosome=true;
				counter--;
			}
			counter+=2;
		}
		
		
		
		ArrayList<Chromosome> population = makePopulation(populationSize);
		ArrayList<Algorythm> algorithms= new ArrayList<Algorythm>();
		Selection selecttion=getSelectionMethod(select);
		Crossover crosssover=getCrossoverMethod(cross, crossoverRate);
		
		
//		Algorythm alg= new Algorythm(new SigamaScaling(), new SinglePoint(crossoverRate), population, generations,mutationRate,elitismNumber);
////		Thread thread= new Thread(alg);
////		thread.start();
//		alg.run();
//		alg.printStatistics();
		for(int i=0;i<numOfAlgorythms;i++){
			System.out.println("count num of runs:"+i);
			Algorythm alg= new Algorythm(selecttion, crosssover, population, generations,mutationRate,elitismNumber);
			alg.setMyMutation(mutationType);
			if(printChromosome)
				alg.setPrintChromosome(printChromosome);
//			Thread thread= new Thread(alg);
//			thread.start();
			alg.run();
			algorithms.add(alg);
			
		}
		Algorythm alg=bestRun(algorithms);
		alg.printStatistics();
	}


	private static double calcBestPathExample(int[] bestPathExample) {
		
		Country country=Country.getIns();
		double pathCost=0;
		for (int i = 0; i < 48; i++) {
			pathCost+=country.calcDistance(bestPathExample[i]-1,bestPathExample[(i+1)%48]-1);
		}
		return pathCost;
	}


	private static double calcBestPathfive(ArrayList<City> cities) {
		
		ArrayList<City> visited=new ArrayList<City>();
		visited.add(cities.get(0));
		double minPath=calcRec(cities,visited);
		
		
		return minPath;
	}


	private static double calcRec(ArrayList<City> cities, ArrayList<City> visited) {
		
		if(visited.size()==5)
			return calcDis(visited.get(0),visited.get(visited.size()-1));
		
		double min=Double.MAX_VALUE;
		for (int i=0; i<5; ++i) {
			if(!visited.contains(cities.get(i)))
			{
				ArrayList<City> newVisited= (ArrayList<City>) visited.clone();
				newVisited.add(cities.get(i));
				double dis=calcDis(cities.get(i),visited.get(visited.size()-1))+
						calcRec(cities,newVisited);
				if(dis<min)
					min=dis;
			}
		}
		
		return min;
		
	}


	private static double calcDis(City city1, City city2) {
		double xDist=city1.getxPos()-city2.getxPos();
		double yDist=city1.getyPos()-city2.getyPos();
		double dist=Math.sqrt(Math.pow(xDist, 2)+Math.pow(yDist,2));
		return dist;
	}


	private static ArrayList<Chromosome> makePopulation(int populationSize) {
		// TODO Auto-generated method stub
		ArrayList<Chromosome> population = new ArrayList<Chromosome>();
		for (int i = 0; i < populationSize; i++)
			population.add(Chromosome.makeChromosome());
		
		return population;
	}
	static Selection getSelectionMethod(SelectionEnum selection){
		if(selection==SelectionEnum.RANK)
			return new RankSelection();
		else if(selection==SelectionEnum.TOURNAMENT)
			return new Tournament();
		else if(selection==SelectionEnum.SIGMA)
			return new SigamaScaling();
		else
			return new Roulette();
	}
	static Crossover getCrossoverMethod(CrossoverEnum crossover,double crossoverRate){
		if(crossover==CrossoverEnum.SINGLE)
			return new SinglePoint(crossoverRate);
		else if(crossover==CrossoverEnum.OX1)
			return new OX1(crossoverRate);
		else
			return new PMX(crossoverRate);
	}
	private static int getGeneration(Algorythm alg){
		ArrayList<ArrayList<Chromosome>> statistics =alg.getStatistics();
		int i=0;
		for (Iterator iterator = statistics.iterator(); iterator.hasNext();i++) {
			
			ArrayList<Chromosome> arrayList = (ArrayList<Chromosome>) iterator
					.next();
			if(alg.getBest(arrayList).getFitness()==200)
				return i;
		}
		return i;
		
	}
	private static Algorythm bestRun(ArrayList<Algorythm> algorithms){
		Algorythm minAlg=algorithms.get(0);
		int minIndex=getGeneration(minAlg);
		for (Iterator iterator = algorithms.iterator(); iterator.hasNext();) {
			Algorythm algorythm = (Algorythm) iterator.next();
			int index=getGeneration(algorythm);
			if(index<minIndex){
				minAlg=algorythm;
				minIndex=index;
				
			}
			
		}
		return minAlg;
	}
	

}
